/*
   JStepPanel.java
   (C) 2007 - Michael Ocean - Boston University

  This work is a part of the larger snBench project.  Information
  can be found at: http://csr.bu.edu/snbench

  This program is free software; you can redistribute it and/or
  modify it under the terms of Version 2 of the GNU General Public 
  License.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package stepide.STEPVisualizer;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.LayoutManager;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;
import javax.swing.border.LineBorder;

import org.jgraph.JGraph;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultPort;
import org.jgraph.graph.GraphCell;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphSelectionModel;
import org.jgrapht.ext.JGraphModelAdapter;
import org.jgrapht.graph.ListenableDirectedGraph;

//import com.jgraph.layout.JGraphFacade;
//import com.jgraph.layout.tree.JGraphTreeLayout;

import step.AbstractTrigger;
import step.ConditionalNode;
import step.DuplicateID;
import step.Graph;
import step.LastTriggerEval;
import step.Node;
import step.ReceiverSocketNode;
import step.SenderSocketNode;
import step.SensorNode;
import step.ValueNode;
import step.exceptions.STEPException;
import stepide.PrintableStepGraph;

/**
 * @author Michael
 *
 *
 * JStepPanel
 * StepViz / JStepPanel.java
 */
public class JStepPanel extends JPanel {

    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	// an adapter required to transform the JGraphT datamodel to a viewable JGraph
    private JGraphModelAdapter<FakeStepNode, DefaultEdge> m_jgAdapter;
    private JGraph m_JGraph;
    private ListenableDirectedGraph<FakeStepNode, DefaultEdge> datamodelgraph;
    
    private JStepPanelEventListener EventListener = null;
    
	public enum ColorMode{ 
		NODETYPE, BINDING;
	} 
    private ColorMode ColorSetting = ColorMode.NODETYPE;
	
    // the actual step Graph to be rendered
    protected step.Graph stepg = null;
    protected HashMap<step.Node, FakeStepNode> hmItems;
    
    public String getXml(){
    	return stepg.Serialize();
    }
    
    public void addStepPanelEventListener(JStepPanelEventListener _responder){
    	EventListener = _responder;
    }
    
    public void SetColorMode(ColorMode cm){
    	ColorSetting = cm;
    	modVertices();
    }
    
    public boolean m_edittable = false;
    
	/**
	Effects: Zooms the graph "clicks" times, either in or out depending on clicks being negative or positive.
	*/
	public void Zoom(int clicks) 
	{
		if(clicks==0)
			return;
		double newscale = (.25 * clicks) + m_JGraph.getScale();
		if(newscale < 0)
			m_JGraph.setScale(.25);
		if(newscale > 2)
			m_JGraph.setScale(2);
		else
			m_JGraph.setScale(newscale);
	}
	    
    public class FakeStepNode {
    	step.Node _node;

    	public String toString() {
    		// TODO Auto-generated method stub
    		return _node.toString();
    	}
    	
    	public FakeStepNode(step.Node n){
    		_node = n;
    	}
    	
    	public step.Node getNode(){
    		return _node;
    	}
    }
    
    /** 
     * Intercepts events in the graph and can call Listener methods
     * when attached.
     *
     *
     * LocalEventManager
     * step.STEPVisualizer / JStepPanel.java
     */
    public class LocalMouseEventRelay implements MouseListener, MouseWheelListener{

		public void mouseClicked(MouseEvent e) {
			/* if there's no listener, don't proceed */
			if(EventListener==null)
				return;
			
			/* we don't support multiple entity modification */
			if(m_JGraph.getSelectionCount() > 1)
				return;
				
			/* handle canvase events */
			if(m_JGraph.getSelectionCount() == 0){
				if(e.getButton()==MouseEvent.BUTTON1){
					if (e.getClickCount() >= 2){
						EventListener.CanvasDoubleClicked(e.getX(),e.getY());
					}
					else{
						EventListener.CanvasLeftClicked(e.getX(),e.getY());
					}
				}else if (e.getButton()==MouseEvent.BUTTON3){
					EventListener.CanvasRightClicked(e.getX(),e.getY());
				}
				return;
			}
			
			/* handle node click events */
			Object eClicked = m_JGraph.getSelectionCell();
			if(eClicked instanceof DefaultEdge){
				DefaultEdge edge = (DefaultEdge)eClicked;
				FakeStepNode fsnS =  (FakeStepNode)((DefaultGraphCell)((DefaultPort)edge.getSource()).getParent()).getUserObject();
				FakeStepNode fsnT =  (FakeStepNode)((DefaultGraphCell)((DefaultPort)edge.getTarget()).getParent()).getUserObject();
				
				if(e.getButton()==MouseEvent.BUTTON1){
					if (e.getClickCount() >= 2){
						EventListener.EdgeDoubleClicked(fsnS,fsnT,e.getX(),e.getY());
					}
					else{
						EventListener.EdgeLeftClicked(fsnS,fsnT,e.getX(),e.getY());
					}
				}else if (e.getButton()==MouseEvent.BUTTON3){
					EventListener.EdgeRightClicked(fsnS,fsnT,e.getX(),e.getY());
				}
			}
			else if(eClicked instanceof DefaultGraphCell){
				DefaultGraphCell cell = (DefaultGraphCell) eClicked;
				if(cell != null){
					Object o = cell.getUserObject();
					if(o instanceof FakeStepNode){					
						FakeStepNode fsn =  (FakeStepNode) cell.getUserObject();
	
						if(e.getButton()==MouseEvent.BUTTON1){
							if (e.getClickCount() >= 2){
								EventListener.NodeDoubleClicked(fsn,e.getX(),e.getY());
							}
							else{
								EventListener.NodeLeftClicked(fsn,e.getX(),e.getY());
							}
						}else if (e.getButton()==MouseEvent.BUTTON3){
							EventListener.NodeRightClicked(fsn,e.getX(),e.getY());
						}
					}
				}
			}

					}
			

		public void mouseEntered(MouseEvent e) {
			// TODO Auto-generated method stub
			
		}

		public void mouseExited(MouseEvent e) {
			// TODO Auto-generated method stub
			
		}

		public void mousePressed(MouseEvent e) {
			// TODO Auto-generated method stub
			
		}

		public void mouseReleased(MouseEvent e) {
			// TODO Auto-generated method stub
			
		}


		public void mouseWheelMoved(MouseWheelEvent e) {
			Zoom(e.getWheelRotation());
			
		}
    	
    }
    
    ArrayList<Node> newlist;
    void RecurseBuiltNodeList(Node n){
		if(!newlist.contains(n)){
			newlist.add(n);
		}
		/* see if it's safe to recurse into this node's children */
		if(!n.IsLeaf()){
			for(int i = 0; i < n.getChildren().length ; i++){
				Node k = n.getChildren()[i];
				RecurseBuiltNodeList(k);
			}
		}
    }
    
    /**
     * Call this to re-draw the current graph from scratch
     * 
     * @param g
     */
    public void RePopulateWithNewGraph(Graph g){
    	stepg = g;
    	RefreshFromGraph();
    }
    
    /** removes all contents and starts over with a Graph.newEmptyGraph
     */
    synchronized public void ClearCurrentGraph(){
    	Collection<DefaultEdge> c = new ArrayList<DefaultEdge>();
    	for(DefaultEdge e : datamodelgraph.edgeSet()){
    		c.add(e);
    	}
    	
    	datamodelgraph.removeAllEdges(c);
    	datamodelgraph.removeAllVertices(hmItems.values());

    	hmItems.clear();
    	stepg = Graph.newEmptyGraph();
    	//LayoutGraph();
    }
    
    /**
     * Creates a directed graph based on a STEP graph object
     *
     * @return a data model graph (JGraphT) based on step.Node objects.
     */
    //private boolean populateJGraphTwithSTEP(ListenableDirectedGraph<step.Node, DefaultEdge> datamodelgraph)
    public boolean populateJGraphTwithSTEP(Graph _stepg)
    {
    	stepg = _stepg;
    	
    	newlist = new ArrayList<Node>();
    	Node roots[] = stepg.getRoots();
    	for(int i = 0; i < roots.length ;  i++){
    		Node n = roots[i];
    		RecurseBuiltNodeList(n);
    	}
    	
    	for(Node t : newlist){
    		datamodelgraph.addVertex( hmItems.get(t) );
    		modVertex(hmItems.get(t),null);
    		//System.out.println("Adding node " + t.GetID());
    	}
    	   	
		/* Trying a different way to preserve node/child ordering 
    	Node n = null;
		Iterator<Node> it = stepg.GetNodeIterator();
		while(it.hasNext()){
			n = it.next();
			datamodelgraph.addVertex( hmItems.get(n) );
    		modVertex(hmItems.get(n));
    		System.out.println("Adding node " + n.GetID());
    	}
    	
		Node nl[] = stepg.getRoots();
		ArrayList<Node> newlist = new ArrayList<Node>();
		for(int i = 0; i < nl.length ; i++){
			Node n = nl[i];
			if(! newlist.contains(n) )
				newlist.add(n);
		    
		}
		*/
    	
        // add edges to create linking structure
		Node n = null;
		Iterator<Node> it = stepg.GetNodeIterator();
		while(it.hasNext()){
			n = it.next();
    		if(!(n instanceof LastTriggerEval)){
				Node nodeFrom = n;
				Node node = null;
				
    			for(int j=0;j<n.getChildren().length;j++){
    				//modEdge(datamodelgraph.addEdge(hmItems.get(n),hmItems.get(n.getChildren()[j])),String.valueOf(j+1) );
    				
    				node = n.getChildren()[j];
    				
    				if(nodeFrom.equals(node)){
    					// Cycle!?!
    					System.err.println("Node links to itself in graph!?");
    					continue;
    				}

    				Object edge = datamodelgraph.addEdge(hmItems.get(nodeFrom),	hmItems.get(node));
    				
    				if(edge==null){
    					System.err.println("The datamodel would not allow an edge from " + nodeFrom.GetID() + " to " + node.GetID() );
    					System.err.println("vertex from exists? " + hmItems.containsKey(nodeFrom) + " vertex to exists? " + hmItems.containsKey(node));
       					continue;
    				}
    				modEdge(
    						edge,
    						String.valueOf(j+1)
    						);
    				
    			}
    		}
    	}
 
        // update vertices with color properties depending on the STEP node type, etc.
        // modVertices();
                       
		//LayoutGraph();
        
        return true;
    }
    
    /*
    public void LayoutGraph() {
        // the next few steps allow the use of the LayoutPro layout package to 
        // do layout for us.  It consists of passing the roots to a facade and 
        // running a layout based on that facade.
        Object[] roots = getRoots(); 
                                   // NOTE: these should be the root cell(s) of
        						   // the tree(s), not the roots of the graph model.
        
        JGraphFacade facade = new JGraphFacade(m_JGraph, roots); // Pass the facade the JGraph instance    
        
        // preserve the order of the nodes as in the data model in the drawn graph,
        // important since the order of the nodes matters to the semantic of the graph!
        facade.setOrdered(true);
        
        JGraphTreeLayout layout = new JGraphTreeLayout(); // Create an instance of the appropriate layout
        
        layout.setOrientation(SwingConstants.NORTH);
        
        // allow the layout to render "multiple" trees (e.g., multiple roots).  This must be
        // set of the layout before we run the layout!
        layout.setTreeDistance(50);
        layout.setPositionMultipleTrees(true);

        layout.run(facade); // Run the layout on the facade.
        
        Map nested = facade.createNestedMap(true, true); // Obtain a map of the resulting attribute 
        												// changes from the facade
        
        m_JGraph.getGraphLayoutCache().edit(nested); // Apply the results to the actual graph
        // done with layout
		
	}
	*/

    /**
     * Performs a full flush and reload of the JGraph based on the
     * data in the step.Graph.  Recommended for all incrementatl changes.
     * Although the time to execute is longer than a diff-only update, 
     * this ensures the JGraph draws exactly what is in the step.Graph 
     * after modification of the step.Graph
     */
    synchronized public void RefreshFromGraph(){
    	/* full rebuild */    	
    	//datamodelgraph.ed
    	Collection<DefaultEdge> c = new ArrayList<DefaultEdge>();
    	for(DefaultEdge e : datamodelgraph.edgeSet()){
    		c.add(e);
    	}
    	
    	datamodelgraph.removeAllEdges(c);
    	datamodelgraph.removeAllVertices(hmItems.values());

    	hmItems.clear();
    	
    	Iterator<Node> it = stepg.GetNodeIterator();
    	while(it.hasNext()){
    		Node n = it.next();
    		hmItems.put(n, new FakeStepNode(n));
    	}
    	
    	populateJGraphTwithSTEP(stepg);
    }
    
	private void MakeJPanel() {           	
		// initialize the node coloring helpers
		colorWheel = new HashMap<String,Color>();
		rand = new Random();
    	
    	// create a empty data model for the graph -- listenable means updates to the
    	// datamodel will be reflected in the adapter and vice-versa
    	datamodelgraph = 
            new ListenableDirectedGraph<FakeStepNode, DefaultEdge>(DefaultEdge.class);

        // create a visualization via JGraph using an adapter to adapt the JGraphT data model
        // to a "drawable" JGraph
    	m_jgAdapter = new JGraphModelAdapter<FakeStepNode, DefaultEdge>(datamodelgraph);

        // create the visible graph via the adapter
        m_JGraph = new JGraph( m_jgAdapter );

        m_JGraph.getGraphLayoutCache().setFactory(new MyCellViewFactory());
        
        populateJGraphTwithSTEP(stepg);
        
        if(datamodelgraph==null){
        	System.err.println("Could not load the STEP file into the graph");
        	return;
        }
        
        // make the graph un-editable (because for a web UI we don't want edits) and pretty
        m_JGraph.setEditable(false);
        m_JGraph.setAntiAliased(true);
        
        // only allow users to select a single node at a time
        m_JGraph.getSelectionModel().setSelectionMode(GraphSelectionModel.SINGLE_GRAPH_SELECTION);

        // set the relay to catch events and send them to the listener
        m_JGraph.addMouseListener(new LocalMouseEventRelay());
                
   	    // create the scroll pane, insert the graph inside it and add the scroll pane to the
        // applet window
        JScrollPane jsp = new JScrollPane();
        jsp.setViewportView(m_JGraph);
        
        this.setLayout(new BorderLayout());
        this.add( jsp, BorderLayout.CENTER );
                
    }

	// get JGraphT Cells from stepNodes
    private Object[] getRoots(){
    	Object r[] = new GraphCell[stepg.getRoots().length];
    	
    	for(int i=0;i< stepg.getRoots().length;i++){
    	  	r[i] = m_jgAdapter.getVertexCell( stepg.getRoots()[i] );
    	}
    	return r;
    	  
    }

    // Apply color formatting to all nodes/verticies
    private void modVertices() {
		Node n = null;
		Iterator<Node> it = stepg.GetNodeIterator();
		while(it.hasNext()){
			n = it.next();
    		modVertex(hmItems.get(n),null);
		}
	}
    
    
    public void ColorEdges(Color c){    	
    	Node n = null;
		Iterator<Node> it = stepg.GetNodeIterator();
		while(it.hasNext()){
			n = it.next();
			for(Node ch : n.getChildren()){
				ColorEdge(n,ch,c);
			}
		}
    }
    
    /**
     * color the edge between vertices from/to to color c
     * 
     * @param from
     * @param to
     * @param c
     */
    public void ColorEdge(Node nFrom, Node nTo, Color c){
		FakeStepNode fnParent = hmItems.get(nFrom);
		FakeStepNode fnChild = hmItems.get(nTo);

		System.out.println("Coloring edge: " + nFrom.GetID() + " to " + nTo.GetID());
		
		DefaultEdge edge = datamodelgraph.getEdge(fnParent, fnChild);
		if(edge==null){
			System.out.println("No edge in datamodel to mod");
			return;
		}
		
		DefaultGraphCell cell = m_jgAdapter.getEdgeCell((DefaultEdge) edge) ;
		
		Map              attr = cell.getAttributes();
        GraphConstants.setBorderColor(attr,c);

        // add this new property to the cell
        Map<DefaultGraphCell, Map> cellAttr = new HashMap<DefaultGraphCell, Map>(  );
        cellAttr.put( cell, attr );
        
        // commit the change to the adapter
        m_jgAdapter.edit(cellAttr,null,null,null);
    }
    
    // change the viewable label on the edge
    private void modEdge(Object edge, String val){
    	DefaultGraphCell cell = m_jgAdapter.getEdgeCell((DefaultEdge) edge) ;
        Map              attr = cell.getAttributes(  );
        
        GraphConstants.setValue(attr, val);

        GraphConstants.setEditable(attr, false);
        GraphConstants.setBendable(attr, false);
        GraphConstants.setDisconnectable(attr, false);
        GraphConstants.setConnectable(attr, false);
                
        // add this new property to the cell
        Map<DefaultGraphCell, Map> cellAttr = new HashMap<DefaultGraphCell, Map>(  );
        cellAttr.put( cell, attr );
        
        // commit the change to the adapter
        m_jgAdapter.edit(cellAttr,null,null,null);

    }

    /**
     * Send a color to color this node (sending null
     * will set the node's color to be the default
     * color)
     * 
     * @param n
     * @param c
     * @return
     */
    synchronized public boolean ColorNode(step.Node n, Color c){
    	FakeStepNode fsn = hmItems.get(n);
    	if(fsn==null)
    		return false;
    	modVertex(fsn,c);
    	return true;
    }
    

	/* binding-based coloring */
	private HashMap<String, Color> colorWheel;
	protected Random rand = null;

	/* Remember: One-time opcode initialization 
	 * can be done in the object constructor.
	 */
	
    /**
     * an example of modifying some properties of a particular node/vertex
     * @param vertex
     */
    private void modVertex( Object vertex, Color override ) {
        DefaultGraphCell cell = m_jgAdapter.getVertexCell( vertex );
        if(cell==null)
        	System.err.println("ModVertex failed to color " + vertex.toString());
        Map              attr = cell.getAttributes(  );
        Rectangle2D        b    = GraphConstants.getBounds( attr );

        GraphConstants.setBounds( attr, b );
        
        Color col = null; 
        Node n = ((FakeStepNode)vertex).getNode();
        
        if(override!=null)
        	col = override;
        else if(ColorSetting == ColorMode.NODETYPE){
	        if(n instanceof AbstractTrigger)
				col = Color.GREEN;
			else if (n instanceof LastTriggerEval)
				col = Color.CYAN;
			else if (n instanceof ValueNode)
				col = Color.MAGENTA;
			else if (n instanceof ConditionalNode)
				col = Color.BLUE;
			else if (n instanceof SensorNode)
				col = Color.ORANGE;
			else if (n instanceof SenderSocketNode)
				col = new Color(205,180,255);
			else if (n instanceof ReceiverSocketNode)
				col = new Color(205,180,255);
			else
				col = Color.LIGHT_GRAY;
        }
        else{
        	String sBinding = n.getBindTo();
        	col = colorWheel.get(sBinding);
			if(col==null){
				col = new Color(rand.nextInt(256),rand.nextInt(256),rand.nextInt(256));
				colorWheel.put(sBinding,col);
			}
        }
        
        GraphConstants.setBorder(attr, LineBorder.createBlackLineBorder());
        GraphConstants.setBorderColor(attr, Color.BLACK);
        GraphConstants.setBackground(attr, Color.DARK_GRAY);
        GraphConstants.setGradientColor( attr, col );
        GraphConstants.setForeground(attr, Color.WHITE);
        
        GraphConstants.setAutoSize(attr, true);
        
        // if you want to add an icon, it's done like so...
        //GraphConstants.setIcon(attr, new ImageIcon("jgraph.gif"));
        
        Map<DefaultGraphCell, Map> cellAttr = new HashMap<DefaultGraphCell, Map>(  );
        cellAttr.put( cell, attr );
        
        m_jgAdapter.edit(cellAttr,null,null,null);
    }
    
    public JStepPanel(Graph gr){
    	stepg = gr;
    	buildNodeMap();
    	MakeJPanel();
    }
    
	public JStepPanel(Graph gr,ColorMode cm){
		stepg = gr;
		ColorSetting = cm;
    	buildNodeMap();
    	MakeJPanel();
    }

	private void buildNodeMap() {
    	hmItems = new HashMap<Node, FakeStepNode>();
    	Iterator<Node> it = stepg.GetNodeIterator();
    	while(it.hasNext()){
    		Node n = it.next();
    		hmItems.put(n, new FakeStepNode(n));
    	}	
	}
   
	/**
	 * 
	 */
	private JStepPanel() {
		// TODO Auto-generated constructor stub
		MakeJPanel();
	}

	/**
	 * @param layout
	 */
	private JStepPanel(LayoutManager layout) {
		super(layout);
		// TODO Auto-generated constructor stub
		MakeJPanel();
	}

	/**
	 * @param isDoubleBuffered
	 */
	private JStepPanel(boolean isDoubleBuffered) {
		super(isDoubleBuffered);
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param layout
	 * @param isDoubleBuffered
	 */
	private JStepPanel(LayoutManager layout, boolean isDoubleBuffered) {
		super(layout, isDoubleBuffered);
		// TODO Auto-generated constructor stub
		MakeJPanel();
	}

	
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {

		if(args.length<1){
			System.err.println("supply a step file to view as an argument.");
			System.exit(-1);
		}
		String sFileName = args[0];
		
		boolean bResolveSplices = false;
		
		if(args.length>1){
			if(args[1].equalsIgnoreCase("true"))
				bResolveSplices = true;
		}
		step.Graph g = step.Graph.LoadGraphFromFile(sFileName, bResolveSplices);
		
		JFrame jf = new JFrame("STEP Viewer " + g.GetID());
		jf.setPreferredSize(new Dimension(800,600));
		jf.add(new JStepPanel(g));

		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);


		jf.pack();
		
		jf.setVisible(true);
		
	}

	public boolean removeStepNode(Node n){
		FakeStepNode fsn = hmItems.remove(n);
		stepg.RemoveNodeAndDisconnect(n.GetID());

		/* re-number all nodes */
		 // add edges to create linking structure
		n = null;
		Iterator<Node> it = stepg.GetNodeIterator();
		while(it.hasNext()){
			n = it.next();
    		if(!(n instanceof LastTriggerEval)){
    			for(int j=0;j<n.getChildren().length;j++)
    				modEdge(datamodelgraph.getEdge(hmItems.get(n),hmItems.get(n.getChildren()[j])),String.valueOf(j+1) );
    		}
    	}

		datamodelgraph.removeVertex( fsn );

		return true;
	}
	
	public boolean removeStepEdge(Node nParent, Node nChild){
		nParent.RemoveChildrenRef(nChild);
		nChild.RemoveParentRef(nParent);

		FakeStepNode fnChild = hmItems.get(nChild);
		FakeStepNode fnParent = hmItems.get(nParent);

		datamodelgraph.removeEdge(fnParent, fnChild);

		/* re-number children */
		for(int i = 0; i < nParent.getChildren().length; i++){
			Node n = nParent.getChildren()[i];
			modEdge(datamodelgraph.getEdge(fnParent,hmItems.get(n)),String.valueOf(i+1));
		}

		return true;
	}
		
	
	public void addStepNode(Node n) throws DuplicateID {
		boolean nodeAdded = false;
		nodeAdded = stepg.AddNode(n);
			
		if(nodeAdded){
			FakeStepNode fsn = new FakeStepNode(n);
			hmItems.put(n,fsn);
			datamodelgraph.addVertex( fsn );
			modVertex(fsn,null);
		}
	}

	public boolean addEdge(Node nodeFrom, Node node) {
		// check the graph to tell if the edge is legal (e.g., not redundant)
		if(nodeFrom.equals(node)){
			return false;
		}
		
		if(nodeFrom.hasChild(node.GetID())||node.hasChild(nodeFrom.GetID()))
			return false;
			
		Object edge = datamodelgraph.addEdge(hmItems.get(nodeFrom),	hmItems.get(node));
		if(edge==null)
			return false;
		modEdge(
				edge,
				String.valueOf(nodeFrom.getChildren().length + 1)
				);
		
		// if allowed by the editor, add it to the step file
		stepg.GetNode(nodeFrom.GetID()).AddChild(stepg.GetNode(node.GetID()));
		
		return true;
	}

	public String getGraphID() {
		return stepg.GetID();
	}
	
	public void setGraphID(String sID){
		stepg.SetID(sID);
	}

	/**
	Effects: Returns a list of IDs all possible splice targets in this graph
	*/
	public String[] getPossibleSpliceTargets()
	{
		step.Node node = null;
		Iterator<step.Node> it = stepg.GetNodeIterator();
		ArrayList<String> nodes = new ArrayList<String>();
		while(it.hasNext()){				
			node = it.next();
			
			if(!(node instanceof step.SpliceNode))
			{
				nodes.add(node.GetID());
			}
		}
		return nodes.toArray(new String[0]);
	}
	
	/**
		Effects:  Returns a list of all possible Last Trigger EVal target trigger Ids
	*/
	public String[] getPossibleLTETriggerIds()
	{
		step.Node node = null;
		Iterator<step.Node> it = stepg.GetNodeIterator();
		ArrayList<String> nodes = new ArrayList<String>();
		while(it.hasNext()){				
			node = it.next();
			
			if(node instanceof step.AbstractTrigger)
			{
				nodes.add(node.GetID());
			}
		}
		return nodes.toArray(new String[0]);
	}

	public BufferedImage getImage() {
		return m_JGraph.getImage(null, 5);
	}

	public ArrayList<STEPException> ValidateSTEP()
	{
		ArrayList<STEPException> errors = new ArrayList<STEPException>();
		step.Node node = null;
		Iterator<step.Node> it = stepg.GetNodeIterator();
		
		while(it.hasNext()){				
			node = it.next();
			node.validate(stepg, errors);
		}
		
		return errors;
	}
	
	public PrintableStepGraph getPrintable() 
	{
		return new PrintableStepGraph(m_JGraph);
	}

	public void removeSelected() {		
		Object eClicked = m_JGraph.getSelectionCell();
		if(eClicked instanceof DefaultEdge){
			DefaultEdge edge = (DefaultEdge)eClicked;
			FakeStepNode fsnS =  (FakeStepNode)((DefaultGraphCell)((DefaultPort)edge.getSource()).getParent()).getUserObject();
			FakeStepNode fsnT =  (FakeStepNode)((DefaultGraphCell)((DefaultPort)edge.getTarget()).getParent()).getUserObject();
			removeStepEdge(fsnS.getNode(), fsnT.getNode());
		}	
		else if(eClicked instanceof DefaultGraphCell){
			DefaultGraphCell cell = (DefaultGraphCell) eClicked;
			if(cell != null){
				Object o = cell.getUserObject();
				if(o instanceof FakeStepNode){					
					FakeStepNode fsn =  (FakeStepNode) cell.getUserObject();
					removeStepNode(fsn.getNode());
				}
			}
		}
	}

	public String requestNextNodeID(String prefix) {
		if(prefix==null)
			return stepg.ProduceID("");
		else
			return stepg.ProduceID(prefix);
		
	}
}
